#include "parser.h"
#include "connection.h"
#include "common/log.h"

#include <regex>


namespace evm {
    namespace http {

        Url::Url(const string &url) {
            http_parser_url u;
            http_parser_parse_url(url.data(), url.size(), 0, &u);
            if (u.field_set & (1 << UF_PATH))
                path.assign(url.data() + u.field_data[UF_PATH].off, u.field_data[UF_PATH].len);
            string query;
            if (u.field_set & (1 << UF_QUERY))
                query.assign(url.data() + u.field_data[UF_QUERY].off, u.field_data[UF_QUERY].len);

            if (!query.empty()) {
                static const regex query_pattern(
                        "(^|&)"  /*start of query or start of parameter "&"*/
                        "([^=&]*)=?"  /*parameter name and "=" if value is expected*/
                        "([^=&]*)"  /*parameter value*/
                        "(?=(&|$))"  /*forward reference, next should be end of query or
                                       start of next parameter*/);
                cregex_iterator begin_iter(query.data(), query.data() + query.size(), query_pattern);
                cregex_iterator end_iter;
                for (auto iter = begin_iter; iter != end_iter; ++iter) {
                    if (iter->length(2) == 0)  // key is empty, ignore it
                        continue;
                    query_paras.emplace(
                            string((*iter)[2].first, (*iter)[2].second),  // parameter name
                            string((*iter)[3].first, (*iter)[3].second)  // parameter value
                    );
                }
            }

        }

        Parser::Parser(HTTPConnection *handler)
        : handler_(handler), request_(handler->request_) {
            http_parser_init(this, HTTP_REQUEST);
        }

        bool Parser::check_version(int major, int minor) const
        {
            return http_major == major && http_minor == minor;
        }

        const string& Parser::get_header(const string &field) const {
            return request_.get_header(field);
        }

        bool Parser::has_header(const string &field, const string &value) const {
            return request_.has_header(field, value);
        }

        Request& Parser::get_request() {
            return request_;
        }

        void Parser::clear() {
            request_.clear();
            total_parsed = 0;
            header_building_state_ = 0;
            header_field_.clear();
            header_value_.clear();
        }

        size_t Parser::execute(const char *buffer, size_t length) {
            static const http_parser_settings settings{
                    on_message_begin,
                    on_url,
                    nullptr,
                    on_header_field,
                    on_header_value,
                    on_headers_complete,
                    on_body,
                    on_message_complete,
                    on_chunk_header,
                    on_chunk_complete
            };

            size_t nparsed = http_parser_execute(this, &settings, buffer, length);
            auto error = HTTP_PARSER_ERRNO(this);
            if (error != HPE_OK && error != HPE_CB_headers_complete)
                throw invalid_argument(fmt::format("http parse error: {}", error));
            total_parsed += nparsed;
            return nparsed;
        }

        void Parser::end() {
            execute(nullptr, 0);  // tell http_parser about EOF
        }

        int Parser::on_message_begin(http_parser* hp) {
            log_info("on_message_begin");
            Parser* parser = static_cast<Parser*>(hp);
            parser->clear();

            return 0;
        }

        int Parser::on_url(http_parser* hp, const char* at, size_t length) {
            log_info("on_url: {}", string(at, at+length));
            Parser* parser = static_cast<Parser*>(hp);
            parser->request_.url.insert(parser->request_.url.end(), at, at + length);

            return 0;
        }

        int Parser::on_header_field(http_parser* hp, const char* at, size_t length) {
            log_info("on_header_field: {}", string(at, at+length));
            Parser* parser = static_cast<Parser*>(hp);
            switch (parser->header_building_state_) {
                case 0:
                    if (!parser->header_value_.empty())
                    {
                        parser->request_.headers.emplace(std::move(parser->header_field_), std::move(parser->header_value_));
                    }
                    parser->header_field_.assign(at, at+length);
                    parser->header_building_state_ = 1;
                    break;
                case 1:
                    parser->header_field_.insert(parser->header_field_.end(), at, at + length);
                    break;
                default:
                    break;
            }

            return 0;
        }

        int Parser::on_header_value(http_parser* hp, const char* at, size_t length) {
            log_info("on_header_value: {}", string(at, at+length));
            Parser* parser = static_cast<Parser*>(hp);
            switch (parser->header_building_state_)
            {
                case 0:
                    parser->header_value_.insert(parser->header_value_.end(), at, at + length);
                    break;
                case 1:
                    parser->header_value_.assign(at, at + length);
                    parser->header_building_state_ = 0;
                    break;
                default:
                    break;
            }

            return 0;
        }

        int Parser::on_headers_complete(http_parser* hp) {
            Parser* parser = static_cast<Parser*>(hp);
            if (!parser->header_field_.empty())
            {
                parser->request_.headers.emplace(std::move(parser->header_field_), std::move(parser->header_value_));
            }

            auto& req = parser->request_;
            req.method = (HTTPMethod)parser->method;

            Url url(req.url);
            req.path = std::move(url.path);
            req.query_paras = std::move(url.query_paras);
            log_info("on_headers_complete: {}", req.str());

            if (!parser->handler_->handle_header()) {
                return -1;  // terminate parsing
            }

            return 0;
        }

        int Parser::on_body(http_parser* hp, const char* at, size_t length) {
            log_info("on_body");
            Parser* parser = static_cast<Parser*>(hp);

            parser->request_.body.insert(parser->request_.body.end(), at, at + length);

            return 0;
        }

        int Parser::on_message_complete(http_parser* hp) {
            log_info("on_message_complete");
            Parser* parser = static_cast<Parser*>(hp);

            parser->handler_->handle_body();

            return 0;
        }

        int Parser::on_chunk_header(http_parser*) {
            log_info("on_chunk_header");
            return 0;
        }

        int Parser::on_chunk_complete(http_parser*) {
            log_info("on_chunk_complete");
            return 0;
        }

    }
}
